Repository navigation
feat(scope): read the targets of web and TLS tools (step 5, PR 4b) - #20
Conversation
Phase 10, step 5 plan, PR 4b. The engagement scope guard now reads the targets of curl, wget, whatweb, nikto, gobuster, ffuf, feroxbuster, sslscan and testssl.sh, so these may run under an engagement instead of being refused as unparsed. A URL's host is read by a strict grammar of codebud's own; a URL two parsers could read differently (a second @, a backslash, a %-escape in the host) is refused. Proxies, curl --resolve and --connect-to hosts, DNS and DoH servers are targets; a proxy on this machine (Burp) is not, since it passes requests on to the URL's own host. Following redirects is refused (curl -L, -r of the fuzzers, nikto -followredirects); wget and whatweb, which follow them by default, need --max-redirect=0 and --follow-redirect=never. A fuzz keyword (FUZZ, or one named in -w file:KEYWORD) is allowed in the path, query and headers, never before the path: the wordlist is not read, and a word like evil.example/# would become the host. feroxbuster joins words to the URL, so its wordlist is read and a word that would leave the host (//x, https:x) refuses it. Config files a step could write in the project (ferox-config.toml, nikto.conf), option files (curl -K, wget -e/--config, nikto -config, ffuf -request), target files and mass-testing modes are refused, as are gobuster's fuzz, s3, gcs and tftp modes. gobuster dns is passive: out of scope it asks. * src/codebud_sec/scope.py (_host_port, _url, _proxy, _loopback) (_no_local_config, _need_targets, _wordlist_problem): New helpers. (_curl, _wget, _whatweb, _nikto, _gobuster, _ffuf, _feroxbuster) (_sslscan, _testssl): New parsers. (PARSERS): Add them. * tests/test_scope_web.py: New file. * tests/test_scope.py (test_an_unparsed_network_tool_is_refused): Use socat; curl has a parser now. * docs/chapters/architecture.texi, docs/chapters/development.texi: Describe them.
Review summary: changes requestedOne full sweep over Conditions. For a network step naming a parsed tool, the step is refused ⇔ any of these:
A passive tool with a target out of scope ( Correct, verified (93 tests; each refusal checked to fire for its stated reason, not by accident):
Defects (each reproduced as ALLOWED on
Verdict: changes requested. The fixes come as new commits, then a re-review. |
Review of #20. env HTTPS_PROXY=... curl, env WGETRC=./x wget or env HOME=. curl give the tool a proxy, or a config file a step can write, that its words do not show, so the guard judged the wrong hosts. An assignment before a parsed tool is now refused, except for locale, time zone and terminal names. * src/codebud_sec/scope.py (_ASSIGNMENT, _HARMLESS_ENV): New. (targets_of): Refuse other assignments before a parsed tool. * tests/test_scope_web.py (test_environment_assignments_before_a_tool_are_refused) (test_harmless_environment_assignments_are_fine): New tests.
Review of #20. sslscan takes values only as --opt=value, and --xmpp-server is a flag, so taking the next word as a value judged a host it does not scan (sslscan --certs evil.example.org app...). Values are now accepted only after =, --xmpp-server is a flag, and one host is taken unless --targets=file lists them. testssl.sh --phone-out contacts the CRL and OCSP servers of third parties; it was read as taking a value, and is now refused. * src/codebud_sec/scope.py (_list_file): Take a spec function. (_SSLSCAN_VALUE): Remove. (_SSLSCAN_PATTERNS): Values only after =; --xmpp-server a flag. (_sslscan): One host, or --targets=file. (_TESTSSL_VALUE, _TESTSSL_FLAGS): --phone-out is a flag. (_testssl): Refuse it. * tests/test_scope_web.py (test_sslscan_is_read_as_sslscan_reads_it) (test_sslscan_values_after_equals_are_fine) (test_testssl_phone_out_is_refused): New tests.
Review of #20. gobuster dir appends each word to its URL; without a path, a word like @evil.example.org lands in the authority, so dir mode now needs a URL with a path. feroxbuster joins words to its URL, and the wordlist check missed words the URL standard cleans up into //host (leading control characters, tabs and newlines anywhere, as in /<TAB>/evil); it now cleans up as WHATWG URL does first. It refused words with any scheme, though only special schemes reach a host, so javascript: and mailto: lines no longer refuse a wordlist. * src/codebud_sec/scope.py (_url): New argument need_path. (_gobuster): Use it in dir mode. (_JOIN_ESCAPE): Two slashes or backslashes, or a special scheme. (_C0_AND_SPACE): New. (_wordlist_problem): Clean up each word as the URL standard does. * tests/test_scope_web.py (test_gobuster_dir_needs_a_path_to_keep_words_out_of_the_host) (test_feroxbuster_wordlist_is_judged_after_the_url_cleanup) (test_feroxbuster_wordlist_words_that_reach_no_host_are_fine): New.
Re-review summary: fixed, ready to merge once CI passesSecond sweep, over
Erring strict, documented in the code:
1651 tests pass, ruff is clean, and |
|
Merging. All five review findings are fixed and re-verified: environment assignments, sslscan parsing, testssl |
Phase 10, step 5 plan, PR 4b: the scope guard reads the targets of web and TLS tools. Until now a network step running any of them was refused as unparsed.
Tools: curl, wget, whatweb, nikto, gobuster (dir, vhost, dns), ffuf, feroxbuster, sslscan, testssl.sh.
What counts as a target
@, a backslash, or a%-escape in the host.--resolveaddresses and--connect-tohosts, DNS servers, DoH servers, and testssl's--ip.What is refused, and why
curl -L,-rin gobuster, ffuf and feroxbuster,nikto -followredirects. A redirect goes wherever the server says. wget and whatweb follow redirects by default, so they need--max-redirect=0and--follow-redirect=never.FUZZand custom-w file:KWkeywords. The wordlist is never read, so a word likeevil.example/#would become the host. This departs from the plan, which would have allowedFUZZ.<scoped domain>: that is unsafe for the same reason.//x,\\x,https:x) refuse the step, and-wis required.curl -K,wget -e/--config(-e robots=offis allowed),nikto -config,ffuf -request/-config;ffuf -input-cmd;testssl --file,--mx);ferox-config.tomlornikto.confin the project, because a step could have written it.-g;gobuster dnsonly looks names up, so it is passive: out of scope it asks instead of refusing.Verification
make check: 1632 passed, including 93 new tests intests/test_scope_web.py.make distcheckpasses;make infoand makeinfo HTML build without warnings; ruff check and format are clean.